package com.mazaiting.common.crypto

import android.util.Base64
import java.io.UnsupportedEncodingException
import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

/***
 *
 *
 *                                                    __----~~~~~~~~~~~------___
 *                                   .  .   ~~//====......          __--~ ~~
 *                   -.            \_|//     |||\\  ~~~~~~::::... /~
 *                ___-==_       _-~o~  \/    |||  \\            _/~~-
 *        __---~~~.==~||\=_    -_--~/_-~|-   |\\   \\        _/~
 *    _-~~     .=~    |  \\-_    '-~7  /-   /  ||    \      /
 *  .~       .~       |   \\ -_    /  /-   /   ||      \   /
 * /  ____  /         |     \\ ~-_/  /|- _/   .||       \ /
 * |~~    ~~|--~~~~--_ \     ~==-/   | \~--===~~        .\
 *          '         ~-|      /|    |-~\~~       __--~~
 *                      |-~~-_/ |    |   ~\_   _-~            /\
 *                           /  \     \__   \/~                \__
 *                       _--~ _/ | .-~~____--~-/                  ~~==.
 *                      ((->/~   '.|||' -_|    ~~-/ ,              . _||
 *                                 -_     ~\      ~~---l__i__i__i--~~_/
 *                                 _-~-__   ~)  \--______________--~~
 *                               //.-~~~-~_--~- |-------~~~~~~~~
 *                                      //.-~~~--\
 *                               神兽保佑
 *                              代码无BUG!
 * @author mazaiting
 * @date 2020/4/3
 * @description AES 加密
 */
object AesCrypto {
    /**
     * 算法/加密模式/填充模式
     */
    private const val AES_MODE = "AES/CBC/PKCS7Padding"
    /**
     * 算法
     */
    private const val CIPHER = "AES"
    /**
     * 哈希算法
     */
    private const val HASH_ALGORITHM = "SHA-256"
    /**
     * 默认密码
     */
    const val DEFAULT_PASSWORD = "vbYK6dB7"
    /**
     * 偏移字节
     */
    private val IV_BYTES = byteArrayOf(
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00,
        0x00
    )

    /**
     * Generates SHA256 hash of the password which is used as key
     *
     * @param password used to generated key
     * @return SHA256 of the password
     */
    @Throws(NoSuchAlgorithmException::class, UnsupportedEncodingException::class)
    private fun generateKey(password: String): SecretKeySpec {
        // 获取哈希算法
        val digest = MessageDigest.getInstance(HASH_ALGORITHM)
        // 密码数组
        val bytes = password.toByteArray()
        // 计算位置
        digest.update(bytes, 0, bytes.size)
        // 生成密钥
        val key = digest.digest()
        // 加密 KEY
        return SecretKeySpec(key, CIPHER)
    }


    /**
     * Encrypt and encode message using 256-bit AES with key generated from password.
     *
     * @param message  the thing you want to encrypt assumed String UTF-8
     * @param password used to generated key
     * @return Base64 encoded CipherText
     * @throws GeneralSecurityException if problems occur during encryption
     */
    @Throws(GeneralSecurityException::class)
    fun encrypt(message: String, password: String = DEFAULT_PASSWORD): String {
        try {
            // 生成 KEY
            val key = generateKey(password)
            // 加密
            val cipherText = encrypt(key, message.toByteArray())
            // NO_WRAP is important as was getting \n at the end Base 编码
            return Base64.encodeToString(cipherText, Base64.NO_WRAP)
        } catch (e: UnsupportedEncodingException) {
            throw GeneralSecurityException(e)
        }

    }


    /**
     * More flexible AES encrypt that doesn't encode
     *
     * @param key     AES key typically 128, 192 or 256 bit
     * @param message in bytes (assumed it's already been decoded)
     * @return Encrypted cipher text (not encoded)
     * @throws GeneralSecurityException if something goes wrong during encryption
     */
    @Throws(GeneralSecurityException::class)
    private fun encrypt(key: SecretKeySpec, message: ByteArray): ByteArray {
        // 获取 AES 加密模式
        val cipher = Cipher.getInstance(AES_MODE)
        // 创建偏移向量
        val ivSpec = IvParameterSpec(IV_BYTES)
        // 初始化加密模式
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
        // 完成加密
        return cipher.doFinal(message)
    }


    /**
     * Decrypt and decode ciphertext using 256-bit AES with key generated from password
     *
     * @param base64EncodedCipherText the encrpyted message encoded with base64
     * @param password                used to generated key
     * @return message in Plain text (String UTF-8)
     * @throws GeneralSecurityException if there's an issue decrypting
     */
    @Throws(GeneralSecurityException::class)
    fun decrypt(base64EncodedCipherText: String, password: String = DEFAULT_PASSWORD): String {
        try {
            // 生成密钥
            val key = generateKey(password)
            // Base64 解码
            val decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP)
            // 解密
            val decryptedBytes = decrypt(key, decodedCipherText)
            // 返回解密数据
            return String(decryptedBytes)
        } catch (e: UnsupportedEncodingException) {
            throw GeneralSecurityException(e)
        }

    }

    /**
     * More flexible AES decrypt that doesn't encode
     *
     * @param key               AES key typically 128, 192 or 256 bit
     * @param decodedCipherText in bytes (assumed it's already been decoded)
     * @return Decrypted message cipher text (not encoded)
     * @throws GeneralSecurityException if something goes wrong during encryption
     */
    @Throws(GeneralSecurityException::class)
    private fun decrypt(key: SecretKeySpec, decodedCipherText: ByteArray): ByteArray {
        // 获取加密模式
        val cipher = Cipher.getInstance(AES_MODE)
        // 初始化向量
        val ivSpec = IvParameterSpec(IV_BYTES)
        // 解密
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
        // 获取明文
        return cipher.doFinal(decodedCipherText)
    }
}